package com.company.nuwa.redis.wrapper;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>redis hash operations wrapper</p >
 *
 * @author Nikola Tesla
 * @version 1.0
 * @date 2020/12/1 10:26
 */
@Slf4j
public class RedisHashOperationsWrapper {

    private final HashOperations<String, String, String> hashOperations;
    private final StringRedisTemplate stringRedisTemplate;

    public RedisHashOperationsWrapper(StringRedisTemplate stringRedisTemplate) {
        this.hashOperations = stringRedisTemplate.opsForHash();
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * Get value for given {@code hashKey} from hash at {@code key}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction.
     */
    public String getString(String key, String hashKey) {
        Object o = hashOperations.get(key, hashKey);
        return Objects.isNull(o) ? null : o.toString();
    }

    /**
     * Get value for given {@code hashKey} from hash at {@code key}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction.
     */
    public Integer getInt(String key, String hashKey) {
        Object o = hashOperations.get(key, hashKey);
        return Objects.isNull(o) ? 0 : NumberUtils.toInt(o.toString());
    }

    /**
     * Get value for given {@code hashKey} from hash at {@code key}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction.
     */
    public Long getLong(String key, String hashKey) {
        Object o = hashOperations.get(key, hashKey);
        return Objects.isNull(o) ? 0 : NumberUtils.toLong(o.toString());
    }

    /**
     * Get value for given {@code hashKey} from hash at {@code key}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction.
     */
    public Date getDate(String key, String hashKey) {
        Object o = hashOperations.get(key, hashKey);
        if (Objects.isNull(o)) {
            return null;
        }
        long timestamp = NumberUtils.toLong(o.toString());
        if (timestamp == 0) {
            return null;
        }
        return new Date(timestamp);
    }

    /**
     * 获取实体
     *
     * @param key
     * @param hashKey
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T getBean(String key, String hashKey, Class<T> tClass) {
        String value = hashOperations.get(key, hashKey);
        return ValueConverter.getBean(value, tClass);
    }

    /**
     * Get values for given {@code hashKeys} from hash at {@code key}.
     *
     * @param key      must not be {@literal null}.
     * @param hashKeys must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public Map<String, String> multiGet(String key, List<String> hashKeys) {
        List<String> result = hashOperations.multiGet(key, hashKeys);
        Map<String, String> hashResult = Maps.newHashMap();
        for (int i = 0; i < hashKeys.size(); i++) {
            hashResult.put(hashKeys.get(i), result.get(i));
        }
        return hashResult;
    }

    /**
     * Get values for given {@code hashKeys} from hash at {@code key}.
     *
     * @param key      must not be {@literal null}.
     * @param hashKeys must not be {@literal null}.
     * @param tClass
     * @return {@literal null} when used in pipeline / transaction.
     */
    public <T> Map<String, T> multiGet(String key, List<String> hashKeys, Class<T> tClass) {
        List<String> result = hashOperations.multiGet(key, hashKeys);
        Map<String, T> hashResult = Maps.newHashMap();
        for (int i = 0; i < hashKeys.size(); i++) {
            hashResult.put(hashKeys.get(i), ValueConverter.getBean(result.get(i), tClass));
        }
        return hashResult;
    }

    /**
     * Get entry set (values) of hash at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public List<String> values(String key) {
        return hashOperations.values(key);
    }

    /**
     * Get entry set (values) of hash at {@code key}.
     *
     * @param key    must not be {@literal null}.
     * @param tClass
     * @return {@literal null} when used in pipeline / transaction.
     */
    public <T> List<T> values(String key, Class<T> tClass) {
        List<String> values = hashOperations.values(key);
        if (CollectionUtils.isEmpty(values)) {
            return Lists.newArrayList();
        }
        return values.stream().map(val -> ValueConverter.getBean(val, tClass)).collect(Collectors.toList());
    }

    /**
     * Get entire hash stored at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public Map<String, String> entries(String key) {
        return hashOperations.entries(key);
    }

    /**
     * Get entire hash stored at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public <T> Map<String, T> entries(String key,Class<T> tClass) {
        Map<String,String> map = hashOperations.entries(key);
        if(ObjectUtil.isEmpty(map)){
            return Maps.newHashMap();
        }
        return map.entrySet()
                .stream()
                .collect(Collectors.toMap(e->e.getKey(),e->ValueConverter.getBean(e.getValue(), tClass)));
    }

    /**
     * Set the {@code value} of a hash {@code hashKey}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @param value
     */
    public void put(String key, String hashKey, String value) {
        hashOperations.put(key, hashKey, value);
    }

    public void put(String key, String hashKey, Object value, Long expiredSeconds) {
        hashOperations.put(key, hashKey, ValueConverter.serializeToString(value));
        this.stringRedisTemplate.expire(key, expiredSeconds, TimeUnit.SECONDS);
    }



    public <T extends Object> void putAll(String key, Map<String, T> m, Long expiredSeconds) {
        Map<String, String> map = Maps.newHashMap();
        for (Map.Entry<String, T> entry : m.entrySet()) {
            map.put(entry.getKey(), JSONUtil.toJsonStr(entry.getValue()));
        }
        hashOperations.putAll(key, map);
        this.stringRedisTemplate.expire(key, expiredSeconds, TimeUnit.SECONDS);
    }

    /**
     * Get key set (fields) of hash at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public Set<String> keys(String key) {
        return hashOperations.keys(key);
    }

    /**
     * Set multiple hash fields to multiple values using data provided in {@code m}.
     *
     * @param key must not be {@literal null}.
     * @param m   must not be {@literal null}.
     */
    public void putAll(String key, Map<String, String> m) {
        hashOperations.putAll(key, m);
    }

    /**
     * Increment {@code value} of a hash {@code hashKey} by the given {@code delta}.
     *
     * @param key     must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @param delta
     * @return {@literal null} when used in pipeline / transaction.
     */
    public Long increment(String key, String hashKey, long delta) {
        return hashOperations.increment(key, hashKey, delta);
    }


    /**
     * Delete given hash {@code hashKeys}.
     *
     * @param key must not be {@literal null}.
     * @param hashKeys must not be {@literal null}.
     * @return {@literal null} when used in pipeline / transaction.
     */
    public Long delete(String key, String... hashKeys){
        return hashOperations.delete(key,  hashKeys);
    }

}
